Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.kt 41414f832f938b655c97bc07509bc140ea16e2a3 (41414f83) Text, 14.29 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database

Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.database.entity.ContactSettings
Tff7b72import T7ee787org.meshtastic.core.database.entity.DiscoveredNodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.DiscoveryPresetResultEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.DiscoverySessionEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.MeshLog
Tff7b72import T7ee787org.meshtastic.core.database.entity.MetadataEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.MyNodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.NodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.Packet
Tff7b72import T7ee787org.meshtastic.core.database.entity.ReactionEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.TracerouteNodePositionEntity
Tff7b72import T7ee787org.meshtastic.core.model.DataPacket
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetadata
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787org.meshtastic.proto.Position
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlin.test.AfterTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFailsWith
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertTrue

T8b949e/**
* Verifies [DatabaseMerger] folds a secondary transport's DB into the canonical one for the same node: message history
* combines with fresh non-colliding ids and stays searchable, node notes are preserved without clobbering, and
* reactions dedupe on their composite key. This is the data-integrity path for cross-transport DB unification.
*/
Tff7b72class T56d364DatabaseMergerTest Tb4b4b4{

T8b949e// Same physical node reached over two transports → same myNodeNum in both DBs.
Tff7b72private Tff7b72val Te6edf3nodeNum Tff7b72= T79c0ff0Te6edf3x1234

T8b949e// The source DB's file name — the key under which a completed merge is recorded in `dest`.
Tff7b72private Tff7b72val Te6edf3sourceName Tff7b72= Ta5d6ff"Ta5d6ffmeshtastic_database_sourceTa5d6ff"

Tff7b72private Tff7b72val Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabase Tff7b72= Te6edf3getInMemoryDatabaseBuilderTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3buildTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabase Tff7b72= Te6edf3getInMemoryDatabaseBuilderTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3buildTb4b4b4(Tb4b4b4)

Tf0883e@AfterTest
Tff7b72fun Td2a8fftearDownTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3sourceTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Te6edf3destTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffmyNodeTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3MyNodeEntityTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3nodeNumTb4b4b4,
Te6edf3model Tff7b72= Ta5d6ff"Ta5d6ffTBEAMTa5d6ff"Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Ta5d6ff"Ta5d6ff2.5.0Ta5d6ff"Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= T79c0ff1LTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff3T79c0ff0T79c0ff0Te6edf3_000Tb4b4b4,
Te6edf3minAppVersion Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8fftextPacketTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3textTb4b4b4: Tffa657StringTb4b4b4, Te6edf3timeTb4b4b4: Tffa657LongTb4b4b4) Tff7b72= Te6edf3PacketTb4b4b4(
Te6edf3uuid Tff7b72= T79c0ff0LTb4b4b4, T8b949e// auto-generated
Te6edf3myNodeNum Tff7b72= Te6edf3nodeNumTb4b4b4,
Te6edf3port_num Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3contact_key Tff7b72= Te6edf3contactTb4b4b4,
Te6edf3received_time Tff7b72= Te6edf3timeTb4b4b4,
Te6edf3read Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3data Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3textTb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3messageText Tff7b72= Te6edf3textTb4b4b4, T8b949e// FTS indexes this column
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffnodeTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3notesTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4) Tff7b72= Te6edf3NodeEntityTb4b4b4(
Te6edf3num Tff7b72= Te6edf3numTb4b4b4,
Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!Tffd700$Te6edf3numTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffNode Tffd700$Te6edf3numTa5d6ff"Tb4b4b4, Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3TBEAMTb4b4b4)Tb4b4b4,
Te6edf3notes Tff7b72= Te6edf3notesTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8fflogEntryTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657StringTb4b4b4, Te6edf3portNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3timeTb4b4b4: Tffa657LongTb4b4b4) Tff7b72= Te6edf3MeshLogTb4b4b4(
Te6edf3uuid Tff7b72= Te6edf3uuidTb4b4b4,
Te6edf3message_type Tff7b72= Ta5d6ff"Ta5d6ffPacketTa5d6ff"Tb4b4b4,
Te6edf3received_date Tff7b72= Te6edf3timeTb4b4b4,
Te6edf3raw_message Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4,
Te6edf3fromNum Tff7b72= T79c0ff3T79c0ff0Tb4b4b4,
Te6edf3portNum Tff7b72= Te6edf3portNumTb4b4b4,
Tb4b4b4)

Tf0883e@Test
Tff7b72fun Td2a8ffmergeFoldsSecondaryTransportIntoCanonicalTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
T8b949e// Canonical (dest): one message, a noted node (#10), an un-noted node (#20), a contact setting, a reaction.
Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setMyNodeInfoTb4b4b4(Te6edf3myNodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffdestmsgTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4, Te6edf3notes Tff7b72= Ta5d6ff"Ta5d6ffkeep meTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4(T79c0ff2T79c0ff0Tb4b4b4)Tb4b4b4) T8b949e// no notes yet
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertContactSettingsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3ContactSettingsTb4b4b4(Te6edf3contact_key Tff7b72= Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Te6edf3muteUntil Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3dest
Tb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3ReactionEntityTb4b4b4(Te6edf3myNodeNum Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3replyId Tff7b72= T79c0ff1Tb4b4b4, Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!uTa5d6ff"Tb4b4b4, Te6edf3emoji Tff7b72= Ta5d6ff"Ta5d6ff👍Ta5d6ff"Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4)
Te6edf3destTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3logEntryTb4b4b4(Ta5d6ff"Ta5d6ffdestlogTa5d6ff"Tb4b4b4, Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4.Te6edf3valueTb4b4b4, Te6edf3time Tff7b72= T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3dest
Tb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3MetadataEntityTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3proto Tff7b72= Te6edf3DeviceMetadataTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4) T8b949e// newer than src

T8b949e// Secondary (source): two messages, notes for #20 (dest blank → fill) and a source-only node (#30 → insert),
T8b949e// the same reaction (dedupe) plus a new one, and a conflicting contact setting (dest's must win).
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setMyNodeInfoTb4b4b4(Te6edf3myNodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffsrcmsgoneTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffsrcmsgtwoTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff3T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4(T79c0ff2T79c0ff0Tb4b4b4, Te6edf3notes Tff7b72= Ta5d6ff"Ta5d6ffsrc noteTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4(T79c0ff3T79c0ff0Tb4b4b4, Te6edf3notes Tff7b72= Ta5d6ff"Ta5d6ffsrc-only noteTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertContactSettingsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3ContactSettingsTb4b4b4(Te6edf3contact_key Tff7b72= Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Te6edf3muteUntil Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3source
Tb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3ReactionEntityTb4b4b4(Te6edf3myNodeNum Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3replyId Tff7b72= T79c0ff1Tb4b4b4, Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!uTa5d6ff"Tb4b4b4, Te6edf3emoji Tff7b72= Ta5d6ff"Ta5d6ff👍Ta5d6ff"Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4)
Te6edf3source
Tb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3ReactionEntityTb4b4b4(Te6edf3myNodeNum Tff7b72= Te6edf3nodeNumTb4b4b4, Te6edf3replyId Tff7b72= T79c0ff1Tb4b4b4, Te6edf3userId Tff7b72= Ta5d6ff"Ta5d6ff!vTa5d6ff"Tb4b4b4, Te6edf3emoji Tff7b72= Ta5d6ff"Ta5d6ff❤️Ta5d6ff"Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff2Tb4b4b4)Tb4b4b4)
T8b949e// Telemetry + position history (audit log) plus a traceroute position referencing a log entry.
Te6edf3sourceTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3logEntryTb4b4b4(Ta5d6ff"Ta5d6ffsrctelemetryTa5d6ff"Tb4b4b4, Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4.Te6edf3valueTb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3logEntryTb4b4b4(Ta5d6ff"Ta5d6ffsrcpositionTa5d6ff"Tb4b4b4, Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4.Te6edf3valueTb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff1T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3source
Tb4b4b4.Te6edf3tracerouteNodePositionDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertAllTb4b4b4(
Te6edf3listOfTb4b4b4(
Te6edf3TracerouteNodePositionEntityTb4b4b4(
Te6edf3logUuid Tff7b72= Ta5d6ff"Ta5d6ffsrcpositionTa5d6ff"Tb4b4b4,
Te6edf3requestId Tff7b72= T79c0ff7Tb4b4b4,
Te6edf3nodeNum Tff7b72= T79c0ff3T79c0ff0Tb4b4b4,
Te6edf3position Tff7b72= Te6edf3PositionTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4(T79c0ff4T79c0ff0Tb4b4b4)Tb4b4b4) T8b949e// source-only node, no notes → still brought over
Te6edf3source
Tb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3MetadataEntityTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3proto Tff7b72= Te6edf3DeviceMetadataTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4) T8b949e// older → dest wins
Te6edf3source
Tb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3MetadataEntityTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0Tb4b4b4, Te6edf3proto Tff7b72= Te6edf3DeviceMetadataTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4) T8b949e// dest lacks → added
Tff7b72val Te6edf3srcSession Tff7b72=
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertSessionTb4b4b4(
Te6edf3DiscoverySessionEntityTb4b4b4(Te6edf3timestamp Tff7b72= T79c0ff1Tb4b4b4, Te6edf3presetsScanned Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4, Te6edf3homePreset Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3srcPreset Tff7b72=
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertPresetResultTb4b4b4(Te6edf3DiscoveryPresetResultEntityTb4b4b4(Te6edf3sessionId Tff7b72= Te6edf3srcSessionTb4b4b4, Te6edf3presetName Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertDiscoveredNodesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3DiscoveredNodeEntityTb4b4b4(Te6edf3presetResultId Tff7b72= Te6edf3srcPresetTb4b4b4, Te6edf3nodeNum Tff7b72= T79c0ff3T79c0ff0LTb4b4b4)Tb4b4b4)Tb4b4b4)

Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4, Te6edf3sourceNameTb4b4b4)

T8b949e// Packets: 1 + 2, with fresh distinct non-zero uuids (no collision with dest's existing uuid).
Tff7b72val Te6edf3packets Tff7b72= Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllPacketsSnapshotTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3packetsTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffall messages combinedTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3packetsTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3uuid Tb4b4b4}Tb4b4b4.Te6edf3toSetTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffuuids are uniqueTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3packetsTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= T79c0ff0L Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffevery merged packet got a real idTa5d6ff"Tb4b4b4)

T8b949e// Merged messages remain searchable via the rebuilt FTS index.
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3searchMessagesTb4b4b4(Ta5d6ff"Ta5d6ffsrcmsgoneTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffcopied message is searchableTa5d6ff"Tb4b4b4)

T8b949e// Node notes: dest's existing note kept, blank note filled from source, source-only node brought over.
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffkeep meTa5d6ff"Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getNodeByNumTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4)Tff7b72?.Te6edf3nodeTff7b72?.Te6edf3notesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffsrc noteTa5d6ff"Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getNodeByNumTb4b4b4(T79c0ff2T79c0ff0Tb4b4b4)Tff7b72?.Te6edf3nodeTff7b72?.Te6edf3notesTb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getNodeByNumTb4b4b4(T79c0ff3T79c0ff0Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffsource-only noted node insertedTa5d6ff"Tb4b4b4)

T8b949e// Reactions dedupe on composite PK: R1 (already in dest) + R2 (new) = 2.
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllReactionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4)

T8b949e// Contact settings: destination's value wins on conflict (IGNORE), never overwritten.
Te6edf3assertEqualsTb4b4b4(
T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllContactSettingsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3contact_key Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff" Tb4b4b4}Tb4b4b4.Te6edf3muteUntilTb4b4b4,
Tb4b4b4)

T8b949e// Telemetry/position history lives in the audit log: both transports' entries combine, and traceroute
T8b949e// positions come along with their (now-present) parent log rows.
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllLogsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6fflog history from both transports combinedTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3destTb4b4b4.Te6edf3tracerouteNodePositionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6fftraceroute positions mergedTa5d6ff"Tb4b4b4)

T8b949e// Source-only nodes are brought over even without notes.
Te6edf3assertNotNullTb4b4b4(Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getNodeByNumTb4b4b4(T79c0ff4T79c0ff0Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffsource-only node brought overTa5d6ff"Tb4b4b4)

T8b949e// Metadata: newest timestamp wins on overlap, source-only rows added.
Tff7b72val Te6edf3metadata Tff7b72= Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllMetadataSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3associateBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3metadataTff7b72[T79c0ff1T79c0ff0Tff7b72]Tff7b72?.Te6edf3timestampTb4b4b4, Ta5d6ff"Ta5d6ffdest's newer metadata keptTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3metadataTff7b72[T79c0ff3T79c0ff0Tff7b72]Tb4b4b4, Ta5d6ff"Ta5d6ffsource-only metadata insertedTa5d6ff"Tb4b4b4)

T8b949e// Discovery: the session → preset → node chain is appended with rewired foreign keys.
Tff7b72val Te6edf3sessions Tff7b72= Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSessionsSnapshotTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3sessionsTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffdiscovery session appendedTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3presets Tff7b72= Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getPresetResultsTb4b4b4(Te6edf3sessionsTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3idTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3presetsTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffpreset appended under rewired session idTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(
T79c0ff1Tb4b4b4,
Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getDiscoveredNodesTb4b4b4(Te6edf3presetsTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4,
Ta5d6ff"Ta5d6ffnode appended under rewired preset idTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffstaleAssociationAtCommitRollsBackEntireMergeTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setMyNodeInfoTb4b4b4(Te6edf3myNodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffmust-not-commitTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72var Te6edf3authorityChecks Tff7b72= T79c0ff0
Te6edf3assertFailsWithTff7b72<Te6edf3StaleAssociationExceptionTff7b72> Tb4b4b4{
Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(
Te6edf3source Tff7b72= Te6edf3sourceTb4b4b4,
Te6edf3dest Tff7b72= Te6edf3destTb4b4b4,
Te6edf3sourceName Tff7b72= Te6edf3sourceNameTb4b4b4,
Te6edf3isAssociationActive Tff7b72= Tb4b4b4{ Te6edf3authorityChecksTff7b72+Tff7b72+ Tff7b72=Tff7b72= T79c0ff0 Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3authorityChecksTb4b4b4, Ta5d6ff"Ta5d6ffauthority is revalidated immediately before commitTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllPacketsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffstale merge writes must roll backTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Tff7b72!Te6edf3destTb4b4b4.Te6edf3mergeMarkerDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3isMergedTb4b4b4(Te6edf3sourceNameTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffstale merge marker must roll backTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ffalreadyMergedPathRechecksAssociationAuthorityTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4, Te6edf3sourceNameTb4b4b4)
Tff7b72var Te6edf3authorityChecks Tff7b72= T79c0ff0

Te6edf3assertFailsWithTff7b72<Te6edf3StaleAssociationExceptionTff7b72> Tb4b4b4{
Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(
Te6edf3source Tff7b72= Te6edf3sourceTb4b4b4,
Te6edf3dest Tff7b72= Te6edf3destTb4b4b4,
Te6edf3sourceName Tff7b72= Te6edf3sourceNameTb4b4b4,
Te6edf3isAssociationActive Tff7b72= Tb4b4b4{ Te6edf3authorityChecksTff7b72+Tff7b72+ Tff7b72=Tff7b72= T79c0ff0 Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3authorityChecksTb4b4b4, Ta5d6ff"Ta5d6ffalready-merged path must revalidate authority before returningTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e/**
* Regression for #6230: a crash after the merge transaction commits but before the address alias is persisted
* re-runs [DatabaseMerger.merge] on the next connection. The non-idempotent tables (packets, discovery) must not be
* duplicated. The durable per-source merge marker, written inside the merge transaction, makes the second merge a
* no-op.
*/
Tf0883e@Test
Tff7b72fun Td2a8ffmergeIsIdempotentAcrossRetryTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setMyNodeInfoTb4b4b4(Te6edf3myNodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffsrcmsgoneTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertTb4b4b4(Te6edf3textPacketTb4b4b4(Ta5d6ff"Ta5d6ff0!broadcastTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffsrcmsgtwoTa5d6ff"Tb4b4b4, Te6edf3time Tff7b72= T79c0ff3T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3srcSession Tff7b72=
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertSessionTb4b4b4(
Te6edf3DiscoverySessionEntityTb4b4b4(Te6edf3timestamp Tff7b72= T79c0ff1Tb4b4b4, Te6edf3presetsScanned Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4, Te6edf3homePreset Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3srcPreset Tff7b72=
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertPresetResultTb4b4b4(Te6edf3DiscoveryPresetResultEntityTb4b4b4(Te6edf3sessionId Tff7b72= Te6edf3srcSessionTb4b4b4, Te6edf3presetName Tff7b72= Ta5d6ff"Ta5d6ffLongFastTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3source
Tb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3insertDiscoveredNodesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3DiscoveredNodeEntityTb4b4b4(Te6edf3presetResultId Tff7b72= Te6edf3srcPresetTb4b4b4, Te6edf3nodeNum Tff7b72= T79c0ff3T79c0ff0LTb4b4b4)Tb4b4b4)Tb4b4b4)

Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4, Te6edf3sourceNameTb4b4b4)

Tff7b72val Te6edf3packetsAfterFirst Tff7b72= Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllPacketsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3size
Tff7b72val Te6edf3sessionsAfterFirst Tff7b72= Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSessionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3size
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3packetsAfterFirstTb4b4b4, Ta5d6ff"Ta5d6ffboth source packets merged onceTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3sessionsAfterFirstTb4b4b4, Ta5d6ff"Ta5d6ffsource discovery session merged onceTa5d6ff"Tb4b4b4)

T8b949e// Simulate the crash-window retry: same source folded into the same dest again.
Te6edf3DatabaseMergerTb4b4b4.Te6edf3mergeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4, Te6edf3sourceNameTb4b4b4)

Te6edf3assertEqualsTb4b4b4(
Te6edf3packetsAfterFirstTb4b4b4,
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllPacketsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4,
Ta5d6ff"Ta5d6ffpackets not duplicated on retryTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(
Te6edf3sessionsAfterFirstTb4b4b4,
Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSessionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3sizeTb4b4b4,
Ta5d6ff"Ta5d6ffdiscovery sessions not duplicated on retryTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.1s